package engine.gameObject.camera;

import java.awt.Point;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;

import engine.gameObject.Camera;

public class FlyoverCamera extends Camera{

	public static final float INITIAL_SPEED = 0.02f;
	public static final float INITIAL_ANGLE_SPEED = (float)Math.toRadians(0.2);

	private boolean forward;
	private boolean backward;
	private boolean left;
	private boolean right;
	
	private float angleFloor;
	private float angleUp; 
	
	private Point lastPos;
	
	private boolean isRightClickDragging;
	
	private float speed;
	private float angleSpeed; 
	
	public FlyoverCamera() {
		lastPos = new Point();
		
		speed = INITIAL_SPEED;
		angleSpeed = INITIAL_ANGLE_SPEED;
		
		setNear(0.1f);
		setNearPane(40);
		setFarPane(100);
		setOpenAngle(45);
		
		setPosition(0, 0, 0, 0, 0);
	}
	
	/**
	 * Set the position of the camera with the angles of the direction of looking.
	 * 
	 * @param posX
	 * @param posY
	 * @param posZ
	 * @param angleFloor
	 * @param angleUp
	 */
	public void setPosition(float posX, float posY, float posZ, double angleFloor, double angleUp) {
		setEyeX(posX);
		setEyeY(posY);
		setEyeZ(posZ);
		this.angleFloor = (float)angleFloor;
		this.angleUp = (float)angleUp;
		
		updateLookPosition();
	}
	
	/**
	 * Sets the movement speed of the camera. 
	 * @param speed
	 */
	public void setSpeed(float speed) {
		this.speed = speed;
	}
	
	/**
	 * Sets the rotation speed of the camera.
	 * @param angleSpeed
	 */
	public void setAngleSpeed(float angleSpeed) {
		this.angleSpeed = angleSpeed;
	}
	
	@Override
	public void update(float timeElapsed) {
		if(forward) {
			moveCamera(speed * timeElapsed);
		}
		
		if(backward) {
			moveCamera(-speed * timeElapsed);
		}
		
		if(left) {
			strafeCamera(speed * timeElapsed);
		}
		
		if(right) {
			strafeCamera(-speed * timeElapsed);
		}
		
		updateLookPosition();
	}
	
	/**
	 * This updates the lookAt position after changing the eye position of the camera. 
	 */
	private void updateLookPosition() {
		float h = (float)Math.cos(angleUp);
		setLookAtX(getEyeX() + (float)Math.sin(angleFloor) * h);
		setLookAtY(getEyeY() + (float)Math.sin(angleUp));
		setLookAtZ(getEyeZ() + (float)Math.cos(angleFloor) * h);
	}
	
	/**
	 * This moves the camera to the left (if move is positive) or to the right (if move is negative). 
	 * 
	 * @param move
	 */
	private void strafeCamera(float move) {
		float angle = angleFloor + (float)Math.PI / 2;
		addX((float)Math.sin(angle) * move);
		addZ((float)Math.cos(angle) * move);
	}
	
	/**
	 * Moves the camera to the front (if move is positive) or to the back (if move is negative). 
	 * @param move
	 */
	private void moveCamera(float move) {
		float h = (float)Math.cos(angleUp);
		addX((float)Math.sin(angleFloor) * move * h);
		addY((float)Math.sin(angleUp) * move);
		addZ((float)Math.cos(angleFloor) * move * h);
	}
	
	/**
	 * Adds x to the current position. 
	 * 
	 * @param x
	 */
	private void addX(float x) {
		setEyeX(getEyeX() + x);
	}
	
	/**
	 * Adds y to the current position. 
	 * 
	 * @param y
	 */
	private void addY(float y) {
		setEyeY(getEyeY() + y);
	}
	
	/**
	 * Adds z to the current position. 
	 * 
	 * @param z
	 */
	private void addZ(float z) {
		setEyeZ(getEyeZ() + z);
	}
	
	/**
	 * Must be called, any time a key is pressed. 
	 * @param key
	 */
	public void keyPressed(int key) {
		switch (key) {
		case Keyboard.KEY_W:
		case Keyboard.KEY_UP:
			forward = true;
			break;
		case Keyboard.KEY_A:
		case Keyboard.KEY_LEFT:
			left = true;
			break;
		case Keyboard.KEY_S:
		case Keyboard.KEY_DOWN:
			backward = true;
			break;
		case Keyboard.KEY_D:
		case Keyboard.KEY_RIGHT:
			right = true;
			break;
		}
	}
	
	/**
	 * Must be called, any time a key is released. 
	 * 
	 * @param key
	 */
	public void keyReleased(int key) {
		switch (key) {
		case Keyboard.KEY_W:
		case Keyboard.KEY_UP:
			forward = false;
			break;
		case Keyboard.KEY_A:
		case Keyboard.KEY_LEFT:
			left = false;
			break;
		case Keyboard.KEY_S:
		case Keyboard.KEY_DOWN:
			backward = false;
			break;
		case Keyboard.KEY_D:
		case Keyboard.KEY_RIGHT:
			right = false;
			break;
		}
	}
	
	/**
	 * Must be called, any time the mouse is pressed. 
	 * 
	 * @param x
	 * @param y
	 */
	public void mousePressed(int x, int y, int button) {
		if(button == 1) {
			isRightClickDragging = true;
			lastPos.setLocation(x, y);
		}
	}
	
	/**
	 * Must be called, any time the mouse is dragged. 
	 * 
	 * @param x
	 * @param y
	 */
	public void mouseDragged(int x, int y, int button) {
		if(isRightClickDragging) {
			angleUp = (float)Math.max(-Math.toRadians(89), Math.min(Math.toRadians(89), angleUp + (y - lastPos.y) * angleSpeed));
			angleFloor += (lastPos.x - x) * angleSpeed;
			
			Mouse.setCursorPosition(lastPos.x, lastPos.y);
		}
	}
	
	/**
	 * Must be called, any time the mouse is released. 
	 */
	public void mouseReleased(int x, int y, int button) {
		isRightClickDragging = false;
	}
}
